/*****************************************************************************
 * hal_esp32_gpio.c
 *
 * Copyright (C) 2020 Jeasonvor 1101627719@qq.com
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 ****************************************************************************/

/*****************************************************************************
 * Included Files
 ****************************************************************************/
#include "plum_api.h"
#include "hal/source/hal_esp32/hal_esp32.h"
/*****************************************************************************
 * Trace Definitions
 ****************************************************************************/
#define LOG_RUN_LEVEL LOG_LEVEL_DEBUG
#define LOG_MODULE    "hal.gpio"
#include "thirdparty/log/log.h"
/*****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/*****************************************************************************
 * Private Types
 ****************************************************************************/
typedef struct {
    plum_u32 id;
    plum_void (*irq_handle)(plum_u32 id);
    struct list_head list;
} hal_esp32_gpio_irq_t;

/*****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

/*****************************************************************************
 * Private Data
 ****************************************************************************/
PLUM_PRIVATE
hal_esp32_gpio_irq_t irq_head;

PLUM_PRIVATE
plum_u64 gpio_sta;

/*****************************************************************************
 * Public Data
 ****************************************************************************/

/*****************************************************************************
 * Private Functions
 ****************************************************************************/

PLUM_PRIVATE
plum_s32 hal_esp32_gpio_dir_init(plum_u32 id, plum_hal_gpio_com_cof_t cof)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        gpio_config_t gpio = {};
        gpio.intr_type     = GPIO_INTR_DISABLE;
        gpio.pin_bit_mask  = (1 << PLUM_HAL_GPIO_PIN(id));
        gpio.pull_up_en    = GPIO_PULLUP_DISABLE;
        gpio.pull_down_en  = GPIO_PULLDOWN_DISABLE;
        gpio.mode          = GPIO_MODE_INPUT;

        switch (cof) {
            case PLUM_HAL_GPIO_MODE_ANALOG:
            case PLUM_HAL_GPIO_MODE_INPUT:
                break;

            case PLUM_HAL_GPIO_MODE_INPUT_UP:
                gpio.pull_up_en = GPIO_PULLUP_ENABLE;
                break;

            case PLUM_HAL_GPIO_MODE_INPUT_DOWN:
                gpio.pull_down_en = GPIO_PULLDOWN_ENABLE;
                break;

            case PLUM_HAL_GPIO_MODE_OUT_PP:
                gpio.mode = GPIO_MODE_OUTPUT;
                break;

            case PLUM_HAL_GPIO_MODE_OUT_PP_UP:
                gpio.mode       = GPIO_MODE_OUTPUT;
                gpio.pull_up_en = GPIO_PULLUP_ENABLE;
                break;

            case PLUM_HAL_GPIO_MODE_OUT_PP_DOWN:
                gpio.mode         = GPIO_MODE_OUTPUT;
                gpio.pull_down_en = GPIO_PULLDOWN_ENABLE;
                break;

            case PLUM_HAL_GPIO_MODE_OUT_OD:
                gpio.mode = GPIO_MODE_OUTPUT_OD;
                break;

            case PLUM_HAL_GPIO_MODE_OUT_OD_UP:
                gpio.mode       = GPIO_MODE_OUTPUT_OD;
                gpio.pull_up_en = GPIO_PULLUP_ENABLE;
                break;

            case PLUM_HAL_GPIO_MODE_OUT_OD_DOWN:
                gpio.mode         = GPIO_MODE_OUTPUT_OD;
                gpio.pull_down_en = GPIO_PULLDOWN_ENABLE;
                break;

            default:
                LOG_E("gpio not support the mode");
                rc = PLUM_ECODE_EPARA;
                break;
        }

        rc = gpio_config(&gpio);
        if (rc) {
            LOG_E("gpio_config err,rc : %d", rc);
            rc = PLUM_ECODE_EIO;
            break;
        }

    } while (0);

    return (rc);
}

PLUM_PRIVATE
gpio_int_type_t hal_esp32_gpio_int_trans(plum_hal_gpio_irq_cof_t cof)
{
    gpio_int_type_t type = GPIO_INTR_MAX;

    switch (cof.trip) {
        case PLUM_HAL_GPIO_IRQ_FALLING_EDGE:
            type = GPIO_INTR_NEGEDGE;
            break;

        case PLUM_HAL_GPIO_IRQ_RISING_EDGE:
            type = GPIO_INTR_POSEDGE;
            break;

        case PLUM_HAL_GPIO_IRQ_BOTH_EDGE:
            type = GPIO_INTR_ANYEDGE;
            break;

        case PLUM_HAL_GPIO_IRQ_HIGH_LEVEL:
            type = GPIO_INTR_LOW_LEVEL;
            break;

        case PLUM_HAL_GPIO_IRQ_LOW_LEVEL:
            type = GPIO_INTR_HIGH_LEVEL;
            break;

        default:
            break;
    }

    return (type);
}

PLUM_PRIVATE
plum_s32 hal_esp32_irq_register(plum_u32 id, plum_void (*cb)(plum_u32 id))
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        hal_esp32_gpio_irq_t *node = plum_null;
        if (!irq_head.list.next) {
            INIT_LIST_HEAD(&irq_head.list);
        }

        node = plum_hal_malloc(sizeof(hal_esp32_gpio_irq_t));
        if (!node) {
            LOG_E("esp32 malloc heap err");
            rc = PLUM_ECODE_EMEM;
            break;
        }
        memset(node, 0, sizeof(hal_esp32_gpio_irq_t));

        node->id         = id;
        node->irq_handle = cb;

        list_add(&node->list, &irq_head.list);
    } while (0);

    return (rc);
}

PLUM_PRIVATE
hal_esp32_gpio_irq_t *hal_esp32_irq_find(plum_u32 id)
{
    hal_esp32_gpio_irq_t *node = plum_null;

    struct list_head *pos;
    list_for_each(pos, &irq_head.list)
    {
        node = list_entry(pos, hal_esp32_gpio_irq_t, list);
        if (node->id == id) {
            break;
        }
    }

    return (node);
}

PLUM_PRIVATE
plum_void hal_esp32_irq_handler(plum_void *arg)
{
    plum_u32 id = (plum_u32)arg;

    hal_esp32_gpio_irq_t *irq = hal_esp32_irq_find(id);
    if (!irq) {
        LOG_E("the interrupt is not register");
        return;
    }

    irq->irq_handle(irq->id);
}

PLUM_PRIVATE
plum_s32 hal_esp32_gpio_irq_init(plum_u32 id, plum_hal_gpio_irq_cof_t cof)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        gpio_int_type_t type = GPIO_INTR_DISABLE;

        gpio_config_t gpio = {};
        gpio.intr_type     = hal_esp32_gpio_int_trans(cof);
        gpio.mode          = GPIO_MODE_INPUT;
        gpio.pin_bit_mask  = (1ULL << PLUM_HAL_GPIO_PIN(id));
        gpio.pull_up_en    = GPIO_PULLUP_DISABLE;
        gpio.pull_down_en  = GPIO_PULLDOWN_DISABLE;

        switch (cof.trip) {
            case PLUM_HAL_GPIO_IRQ_FALLING_EDGE:
                type            = GPIO_INTR_NEGEDGE;
                gpio.pull_up_en = GPIO_PULLUP_ENABLE;
                break;

            case PLUM_HAL_GPIO_IRQ_RISING_EDGE:
                type              = GPIO_INTR_POSEDGE;
                gpio.pull_down_en = GPIO_PULLDOWN_ENABLE;
                break;

            case PLUM_HAL_GPIO_IRQ_BOTH_EDGE:
                type = GPIO_INTR_ANYEDGE;
                break;

            case PLUM_HAL_GPIO_IRQ_LOW_LEVEL:
                type            = GPIO_INTR_LOW_LEVEL;
                gpio.pull_up_en = GPIO_PULLUP_ENABLE;
                break;

            case PLUM_HAL_GPIO_IRQ_HIGH_LEVEL:
                type              = GPIO_INTR_HIGH_LEVEL;
                gpio.pull_down_en = GPIO_PULLDOWN_ENABLE;
                break;

            default:
                break;
        }
        rc = gpio_config(&gpio);
        if (rc) {
            LOG_E("gpio_config err,rc : %d", rc);
            rc = PLUM_ECODE_EIO;
            break;
        }

        rc = gpio_set_intr_type(PLUM_HAL_GPIO_PIN(id), type);
        if (rc) {
            LOG_E("gpio_set_intr_type err,rc : %d", rc);
            rc = PLUM_ECODE_EIO;
            break;
        }

        rc = gpio_install_isr_service(0);
        if (rc) {
            LOG_E("gpio_install_isr_service err,rc : %d", rc);
            rc = PLUM_ECODE_EIO;
            break;
        }

        rc = gpio_isr_handler_add(PLUM_HAL_GPIO_PIN(id), hal_esp32_irq_handler,
                                  (plum_void *)id);
        if (rc) {
            LOG_E("gpio_isr_handler_add err,rc : %d", rc);
            rc = PLUM_ECODE_OK;
            break;
        }

        rc = hal_esp32_irq_register(id, cof.irq_handle);
        if (rc) {
            LOG_E("hal_esp32_irq_register err,rc : %d", rc);
            break;
        }

    } while (0);

    return (rc);
}

/*****************************************************************************
 * Public Functions
 ****************************************************************************/

PLUM_PUBLIC
plum_s32 plum_hal_gpio_init(plum_u32 id, plum_hal_gpio_cof_t *cof)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        if (!PLUM_HAL_ID_IS_GPIO(id)) {
            LOG_E("The gpio id is invalid");
            rc = PLUM_ECODE_INVALID;
            break;
        }

        if (!cof) {
            LOG_E("the config pointer is null");
            rc = PLUM_ECODE_EPARA;
            break;
        }

        if (cof->mode == PLUM_HAL_GPIO_MODE_COMMON) {
            rc = hal_esp32_gpio_dir_init(id, cof->config.com);
        }
        else {
            rc = hal_esp32_gpio_irq_init(id, cof->config.irq);
        }
        if (rc) {
            LOG_E("gpio init err,rc: %d", rc);
            break;
        }
    } while (0);

    return (rc);
}

PLUM_PUBLIC
plum_bit plum_hal_gpio_read(plum_u32 id)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        if (!PLUM_HAL_ID_IS_GPIO(id)) {
            LOG_E("The gpio id is invalid");
            rc = PLUM_ECODE_INVALID;
            break;
        }

        rc = gpio_get_level(PLUM_HAL_GPIO_PIN(id));

    } while (0);

    return (plum_bit)(rc);
}

PLUM_PUBLIC
plum_s32 plum_hal_gpio_write(plum_u32 id, plum_hal_gpio_sta_t sta)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        if (!PLUM_HAL_ID_IS_GPIO(id)) {
            LOG_E("The gpio id is invalid");
            rc = PLUM_ECODE_INVALID;
            break;
        }

        plum_u32 level = (sta == PLUM_HAL_GPIO_TOG) ? 0 : sta;

        if (sta == PLUM_HAL_GPIO_TOG) {
            if (PLUM_GET_BIT(gpio_sta, PLUM_HAL_GPIO_PIN(id))) {
                level = 0;
            }
            else {
                level = 1;
            }
        }

        gpio_set_level(PLUM_HAL_GPIO_PIN(id), level);

        if (level) {
            gpio_sta = PLUM_SET_BIT(gpio_sta, PLUM_HAL_GPIO_PIN(id));
        }
        else {
            gpio_sta = PLUM_CLEAR_BIT(gpio_sta, PLUM_HAL_GPIO_PIN(id));
        }

    } while (0);

    return (rc);
}

PLUM_PUBLIC
plum_void plum_hal_gpio_irq_enable(plum_u32 id)
{
    do {
        if (!PLUM_HAL_ID_IS_GPIO(id)) {
            LOG_E("The gpio id is invalid");
            break;
        }

        gpio_intr_enable(PLUM_HAL_GPIO_PIN(id));
    } while (0);
}

PLUM_PUBLIC
plum_void plum_hal_gpio_irq_disable(plum_u32 id)
{
    do {
        if (!PLUM_HAL_ID_IS_GPIO(id)) {
            LOG_E("The gpio id is invalid");
            break;
        }

        gpio_intr_disable(PLUM_HAL_GPIO_PIN(id));
    } while (0);
}

/****************************************************************************/
/*                                                                          */
/*  End of file.                                                            */
/*                                                                          */
/****************************************************************************/
